/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *              C E D A R
 *          S O L U T I O N S       "Software done right."
 *           S O F T W A R E
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Copyright (c) 2013 Kenneth J. Pronovici.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Apache License, Version 2.0.
 * See LICENSE for more information about the licensing terms.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Author   : Kenneth J. Pronovici <pronovic@ieee.org>
 * Language : Java 6
 * Project  : Common Java Functionality
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
package com.cedarsolutions.client.gwt.rpc.proxy;

import com.cedarsolutions.client.gwt.rpc.IXsrfTokenRpc;
import com.cedarsolutions.client.gwt.rpc.IXsrfTokenRpcAsync;
import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.RpcRequestBuilder;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.rpc.XsrfToken;
import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
import com.google.gwt.user.client.rpc.impl.Serializer;

/**
 * Customized remote service proxy that knows how to make CSRF/XSRF-protected requests.
 *
 * <p>
 * When you request an RPC interface with <code>GWT.create(MyRemoteService.class)</code>,
 * GWT normally does some magic to implement an asynchronous proxy over your
 * remote service interface.  This class is a customized version of GWT's
 * magic asynchronous proxy which knows how to set CSRF/XSRF token into the RPC
 * call once the token is available.  That  way, RPC clients don't need to know
 * anything about the way the service is actually invoked &mdash; it's all
 * controlled by annotations.
 * </p>
 *
 * <p>
 * The customization is conceptually very simple: for every relevant remote procedure
 * call, we want to insert a different RPC call that happens first.  The first RPC
 * call invokes an RPC and gets a cryptographic token that's needed by the real RPC
 * call.  Once we have the token, the real RPC call is made as usual. All of the
 * fancy stuff happens in XsrfRpcProxyCreator.
 * </p>
 *
 * <p>
 * Proxy classes like this are never directly instantiated by the GWT
 * infrastructure.  Instead, they're used as the basis for other concrete proxy
 * classes that are generated by the GWT compiler.  The GWT generator
 * infrastructure generates other classes that extend this class, one concrete
 * proxy implementation for each referenced remote service interface.
 * </p>
 *
 * <p>
 * Configuration for this class feels like a humongous hack.  I would like to
 * make the XSRF RPC timeout configurable.  However, there's no obvious way to
 * do it.  The proxy is instantiated on the client side at runtime, but there's
 * no hook to set properties on the proxy when it's instantiated.  I've fallen
 * back on using the XsrfRpcProxyConfig singleton to hold the value.  See notes
 * in that class for more details.
 * </p>
 *
 * @see <a href="https://developers.google.com/web-toolkit/doc/latest/DevGuideSecurityRpcXsrf">GWT RPC XSRF protection</a>
 * @author Kenneth J. Pronovici <pronovic@ieee.org>
 */
public abstract class XsrfRpcProxy extends RemoteServiceProxy {

    /** Instantiates a new remote service proxy. */
    public XsrfRpcProxy(String moduleBaseURL, String remoteServiceRelativePath,
                        String serializationPolicyName, Serializer serializer) {
        super(moduleBaseURL, remoteServiceRelativePath, serializationPolicyName, serializer);
    }

    /**
     * Get a properly-configured reference to the CSRF/XSRF token RPC.
     * @return Asynchronous reference to the RPC, via the standard GWT.create() mechanism.
     */
    public static IXsrfTokenRpcAsync getXsrfTokenRpc() {
        IXsrfTokenRpcAsync remoteInterface = GWT.create(IXsrfTokenRpc.class);
        ((ServiceDefTarget) remoteInterface).setRpcRequestBuilder(new TimeoutBuilder());
        return remoteInterface;
    }

    /** Callback to handle responses from XsrfTokenServiceAsync. */
    @SuppressWarnings("rawtypes")
    protected abstract class AbstractTokenCallback implements AsyncCallback<String> {
        private AsyncCallback callback;

        public AbstractTokenCallback(AsyncCallback callback) {
            this.callback = callback;
        }

        @Override
        public void onFailure(Throwable caught) {
            this.callback.onFailure(caught);
        }

        @Override
        public void onSuccess(String token) {
            XsrfToken xsrfToken = new XsrfToken(token);
            setRpcToken(xsrfToken);
            this.invokeRpcMethod();
        }

        protected abstract void invokeRpcMethod();
    }

    /** RpcRequestBuilder that sets a timeout. */
    protected static class TimeoutBuilder extends RpcRequestBuilder {
        @Override
        protected RequestBuilder doCreate(String serviceEntryPoint) {
            int timeoutMs = XsrfRpcProxyConfig.getInstance().getConfiguredTimeoutMs();
            GWT.log("XsrfRpcProxy using timeout: " + timeoutMs + " ms");
            RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, serviceEntryPoint);
            builder.setTimeoutMillis(timeoutMs);
            return builder;
        }
    }

}
